#pragma once
#include <iostream>
#include <functional>
using namespace std;
namespace Aurora
{
	template<class T>
	class shared_ptr
	{
	public:
		shared_ptr(T* ptr = nullptr)
			:_ptr(ptr)
			,_pcount(new int(1))
		{}

		// 定制删除器 - 删除器就是一个可调用对象，比如类似于仿函数,lamber。
		template<class D>
		shared_ptr(T* ptr,D del)
			:_ptr(ptr)
			, _pcount(new int(1))
			, _del(del)
		{}

		// sp2(sp1)
		shared_ptr(const shared_ptr<T>& sp)
			:_ptr(sp._ptr)
			,_pcount(sp._pcount)
		{
			++(*_pcount);
		}

		void release()
		{
			if (--(*_pcount) == 0)
			{
				// delete _ptr;
				_del(_ptr);
				delete _pcount;
				_ptr = nullptr;
				_pcount = nullptr;
			}
		}

		// 赋值是已经存在的两个对象，不能直接去管理新资源，要先把自己
		// 现在管理的资源处理好。
		shared_ptr<T>& operator=(const shared_ptr<T>& sp)
		{
			// 析构不建议显示调用，可以调用，并且还是正确的，
			// 有人会专门去看构造和析构的匹配，在这调会导致不匹配
			// 当然，我们可以写一个类似Release的函数把析构的逻辑单独
			// 提取出来
			//this->~shared_ptr();
			if (_ptr != sp._ptr)
			{
				release();

				_ptr = sp._ptr;
				_pcount = sp._pcount;
				++(*_pcount);
			}
			return *this;
		}

		~shared_ptr()
		{
			release();
		}

		T* get()
		{
			return _ptr;
		}

		int use_count()
		{
			return *_pcount;
		}

		T& operator*() { return *_ptr; }
		T* operator->() { return _ptr; }

	private:
		T* _ptr;
		int* _pcount;
		// 包装器可以包装各种可调用对象
		function<void(T* ptr)> _del = [](T* ptr) { delete ptr; };
	};
}

/*

这个shared_ptr有没有移动构造和移动赋值？
1.我们不写编译器会不会默认生成？
不会，移动构造和移动赋值我们不写编译器默认生成的条件是：没有写拷贝
构造，也没有写赋值，也没有写析构，这三个一个都没有写才会自动生成，
而我们的代码是全给写了，全写了就不会生成了。
不会生成，我们也没有写，是左值会走shared_ptr(const shared_ptr<T>& sp)，
是右值会走shared_ptr<T>& operator=(const shared_ptr<T>& sp)，const左值
引用会引用左值也会引用右值。
2.如果是右值，需不需要转移资源？可不可以转移资源？
可以转移资源，但是没必要，我写一个移动构造和移动赋值有必要吗？
是左值，拷贝，加加计数，是右值，把你的资源都转移给我，有必要吗？
没有必要，你是将亡值，是右值的时候就走拷贝构造，无非就是把你的资源的指针
给我，把你的计数给我，我再++计数，你是右值，相当于我仅仅对你进行了拷贝，
拷贝的时候仅仅++了计数而已，紧接着你就析构，析构就再减减一下计数，也就把
你的资源相当于转移给我了。
严格来说，不是深拷贝的类都不需要移动构造
shared_ptr本质还是浅拷贝，我给你一起指向资源，通过计数去释放的问题。
所以这里我们不需要考虑拷贝构造，移动构造的问题。
传值返回也是一样的，先拷贝，再加加计数，里面的生命周期到了再减减计数

浅拷贝的类，移动构造和移动赋值都没有特别大的意义，所以我们不需要实现，
编译器也不会默认生成，就算它生成了，如果是浅拷贝的类，它跟拷贝构造和
赋值是一样的。


*/